home *** CD-ROM | disk | FTP | other *** search
/ Nebula 2 / Nebula Two.iso / Apps / DevTools / eText5 / Source / NewXTeXT / XText0.9beta2 / XText.subproj / XTAction_parser.m < prev    next >
Encoding:
Text File  |  1995-07-28  |  11.4 KB  |  437 lines

  1. #import "ErrorStream.h"
  2. #import "XTAction.h"
  3. #import <sys/types.h>
  4. #import <stdio.h>
  5. #import <stdlib.h>
  6. #import <string.h>
  7. #import <appkit/NXCType.h>;
  8.  
  9. /*  This file contains all the routines to parse the argument to the
  10.     addBindings:estream: method; it's the most complicated part of the
  11.     whole package.  The strategy is simple recursive descent, and we
  12.     make no attempt to recover from errors.
  13.  
  14.     The grammar supported is somewhat more general than necessary; for
  15.     example you can nest sequences of instructions, which currently serves
  16.     no purpose (unless you wanted to get around the maximum sequence
  17.     length...).  The idea is just to make it easy to add more complex
  18.     control structure later, if that turns out to be useful.
  19. */
  20.  
  21. #define MAX_SEQUENCE_LENGTH 16        //    max number of actions in a sequence
  22. #define MAX_SELECTOR_LENGTH 32        //    max length of a selector name
  23. #define MAX_STRING_LENGTH 256        //    max length of a string argument
  24. #define MAX_ARGS 2                    //    max number of args to a message
  25.     // (Note that if you increase MAX_ARGS you'll also have to add a new
  26.     //    subclass of XTAction and augment parse_msg to use it.)
  27.  
  28. #define MAX_KEYS 8                    //    max number of keys affected by a
  29.                                     //    single binding
  30.  
  31. #define PRE_ERROR_CONTEXT 32        //    number of characters displayed before
  32. #define POST_ERROR_CONTEXT 16        //    and after a syntax error
  33.  
  34. typedef charCode keySet[MAX_KEYS];    //    set of keys an action will be bound to
  35.  
  36.  
  37. #define ALPHA(c) \
  38.     (((c >= 'A') && (c <= 'Z')) || ((c >= 'a') && (c <= 'z')) || (c == '_'))
  39.  
  40. #define WHITE(c) \
  41.     ((c == ' ') || (c == '\n') || (c == '\t') || (c == '\r'))
  42.  
  43. #define DIGIT(c) \
  44.     ((c >= '0') && (c <= '9'))
  45.  
  46.  
  47. /*    skip_whitespace advances over any white space and returns the first
  48.     non-whitespace character.
  49.  
  50.     Like the rest of the parsing routines, it's passed a pointer to a
  51.     char pointer, which is advanced as the string is consumed.
  52. */
  53.  
  54. char skip_whitespace(const char **p)
  55. {
  56.     while (WHITE(**p))
  57.         ++*p;
  58.     return **p;
  59. }
  60.  
  61. int HexToDecimal(unsigned char c, unsigned char *error)
  62. {
  63.      *error = 0;
  64.      if (DIGIT(c)) return (int)(c - '0');
  65.      else if ((c >= 'A') && (c <= 'F')) return (int)(10 + c - 'A') ;            
  66.      else {
  67.          *error = 0; 
  68.          return 0;
  69.      }
  70. }
  71.  
  72. int RealChar(unsigned char c, int key)
  73. {
  74.    /* Non-printing control characters (c'a, etc) should have their 
  75.     * character code values reduced by certain amounts */
  76.     
  77.     int control_filter = 4;
  78.     int alt_filter = 8;
  79.     
  80.     if(!(key & control_filter) || (key & alt_filter)) return (int)c;
  81.  
  82.     if( (c >= '@') && (c <= '_') ) return (int)(c - 0x40);
  83.     if( (c >= '`') && (c <= '~') ) return (int)(c - 0x60);
  84.     
  85.     else return (int) c;    
  86.     
  87. }
  88. /*    report_syntax_error is used to report all syntax errors detected during
  89.     parsing.  The args are
  90.         error        optional information about the type of error
  91.         p            a pointer to the place at which the error was detected
  92.         start        a pointer to the beginning of the string being parsed
  93.         errs        the ErrorStream
  94.  
  95.     The message will contain some or all of the string surrounding the
  96.     error to help identify the problem.
  97. */
  98.  
  99. void report_syntax_error(const char *error, const char *p, const char *start,
  100.                          ErrorStream *errs)
  101. {
  102.     char msg[100+PRE_ERROR_CONTEXT+POST_ERROR_CONTEXT];
  103.     const char *prefix;
  104.     int  prefix_length;
  105.  
  106.     // display at most PRE_ERROR_CONTEXT characters before the error point...
  107.  
  108.     if (start < (p - PRE_ERROR_CONTEXT)) {
  109.         prefix = p - PRE_ERROR_CONTEXT;
  110.         prefix_length = PRE_ERROR_CONTEXT;
  111.     } else {
  112.         prefix = start;
  113.         prefix_length = p-start;
  114.     }
  115.  
  116.     // ... and at most POST_ERROR_CONTEXT characters after, except that if
  117.     // there weren't many characters before we can put even more after.
  118.  
  119.     sprintf(msg, "Syntax error%s in binding:\n    %.*s (here) %.*s",
  120.                 error, prefix_length, prefix,
  121.                 (PRE_ERROR_CONTEXT+POST_ERROR_CONTEXT-prefix_length), p);
  122.     [errs report:msg];
  123. }
  124.  
  125. XTAction *parse_action(const char **p, NXZone *z,
  126.                        const char *start, ErrorStream *errs);
  127.  
  128. /*    parse_seq parses a '{}'-delimited, ';'-separated sequence of actions
  129.     and constructs an XTSeqAction out of them.  The args are
  130.         p        pointer to the current position pointer
  131.         z        zone in which to allocate the XTActions
  132.         start    the beginning of the string (for reporting errors)
  133.         errs    the ErrorStream
  134.  
  135.     If there are no errors, the new XTAction is returned; otherwise the
  136.     result is nil.
  137. */
  138.  
  139. XTAction *parse_seq(const char **p, NXZone *z,
  140.                     const char *start, ErrorStream *errs)
  141. {
  142.     // we accumulate the actions in an array on the stack, and then copy
  143.     // them into the specified zone when we find out how many there were.
  144.  
  145.     XTAction *actions[MAX_SEQUENCE_LENGTH];
  146.     int num_actions = 0;
  147.     XTAction **copied_actions = 0;
  148.     char c;
  149.  
  150.     // skip over the open brace
  151.     ++*p;
  152.     while (1) {
  153.         c = skip_whitespace(p);
  154.         if (c == '}') {
  155.             ++*p;
  156.             if (num_actions == 1)
  157.                 return actions[0];
  158.             else if (num_actions > 0) {
  159.                 size_t size = num_actions * sizeof(XTAction *);
  160.                 copied_actions = NXZoneMalloc(z, size);
  161.                 memcpy(copied_actions, actions, size);
  162.             }
  163.             return [[XTSeqAction allocFromZone:z]
  164.                         initLength:num_actions actions:copied_actions];
  165.             }
  166.         else if (c == ';')
  167.             ++*p;
  168.         else {
  169.             if (num_actions >= MAX_SEQUENCE_LENGTH) {
  170.                 report_syntax_error(" (sequence too long)", *p, start, errs);
  171.                 return nil;
  172.             }
  173.             if (!(actions[num_actions++] = parse_action(p, z, start, errs)))
  174.                 return nil;
  175.         }
  176.     }
  177. }
  178.  
  179. /*    parse_arg parses a message argument, which must be either an integer
  180.     or a '"'-delimited string.  The args are the same as parse_seq, with
  181.     one addition:
  182.         result        a pointer to where the result should be stored
  183.  
  184.     Only a few escape sequences are recognized: \n, \t, \\, and \".  It
  185.     would be easy to add more.
  186.  
  187.     If there are no errors, the result (coerced to an int) will be stored
  188.     in *result and parse_arg will return true; otherwise it returns false.
  189. */
  190.  
  191. BOOL parse_arg(int *result, const char **p, NXZone *z,
  192.                const char *start, ErrorStream *errs)
  193. {
  194.     char arg[MAX_STRING_LENGTH];
  195.     int arg_length = 0;
  196.     char c;
  197.     char *copied_arg;
  198.  
  199.     c = skip_whitespace(p);
  200.     if (DIGIT(c) || (c == '-') || (c == '+'))
  201.         *result = strtol(*p, p, 0);        // ought to check for overflow...
  202.     else if (c == '"') {
  203.         while (1) {
  204.             c = *++*p;
  205.             switch (c) {
  206.             case 0:
  207.                 report_syntax_error(" (unterminated string)", *p, start, errs);
  208.                 return NO;
  209.             case '"':
  210.                 ++*p;
  211.                 goto at_end;
  212.             case '\\':
  213.                 c = *++*p;
  214.                 switch (c) {
  215.                 case 'n':    c = '\n'; break;
  216.                 case 't':    c = '\t'; break;
  217.                 case '\\':
  218.                 case '"':              break;
  219.                 default:
  220.                     report_syntax_error(" (unknown escape sequence)",
  221.                                         *p, start, errs);
  222.                     return NO;
  223.                 }
  224.             }
  225.             if (arg_length >= MAX_STRING_LENGTH) {
  226.                 report_syntax_error(" (string too long)", *p, start, errs);
  227.                 return NO;
  228.             }
  229.             arg[arg_length++] = c;
  230.         }
  231.     at_end:
  232.         copied_arg = NXZoneMalloc(z, arg_length+1);
  233.         memcpy(copied_arg, arg, arg_length);
  234.         copied_arg[arg_length] = '\0';
  235.         *result = (int)copied_arg;
  236.     } else {
  237.         report_syntax_error("", *p, start, errs);
  238.         return NO;
  239.     }
  240.     return YES;
  241. }
  242.  
  243. /*    parse_msg parses a single message action, such as
  244.             replaceSel:"foobar" length:3
  245.     The args and result are the same as for parse_seq.
  246. */
  247.  
  248. XTAction *parse_msg(const char **p, NXZone *z,
  249.                     const char *start, ErrorStream *errs)
  250. {
  251.     char sel_name[MAX_SELECTOR_LENGTH];
  252.     int args[MAX_ARGS];
  253.     int sel_length = 0;
  254.     int num_args = 0;
  255.     char c;
  256.     SEL sel;
  257.     char *error;
  258.  
  259.     c = **p;
  260.     while (1) {
  261.         sel_name[sel_length++] = c;
  262.         if (sel_length >= MAX_SELECTOR_LENGTH) {
  263.             error = " (selector too long)";
  264.             goto syntax_error;
  265.         }
  266.         ++*p;
  267.         if (c == ':') {
  268.             if (num_args >= MAX_ARGS) {
  269.                 error = " (too many args)";
  270.                 goto syntax_error;
  271.             }
  272.             if (!parse_arg(&args[num_args++], p, z, start, errs))
  273.                 return nil;
  274.             skip_whitespace(p);
  275.         }
  276.         c = **p;
  277.         if (!(ALPHA(c) || DIGIT(c) || c == ':'))
  278.             break;
  279.     }
  280.     sel_name[sel_length] = '\0';
  281.     sel = sel_getUid(sel_name);
  282.     if (sel == 0) {
  283.         error = " (unknown selector)";
  284.         goto syntax_error;
  285.     }
  286.     return num_args == 0
  287.                 ? [[XTMsg0Action allocFromZone:z] initSel:sel]
  288.         : num_args == 1
  289.                    ? [[XTMsg1Action allocFromZone:z] initSel:sel arg:args[0]]
  290.         : [[XTMsg2Action allocFromZone:z] initSel:sel arg:args[0] arg:args[1]];
  291.  
  292. syntax_error:
  293.     report_syntax_error(error, *p, start, errs);
  294.     return nil;
  295. }
  296.  
  297. /*    parse_action parses an action, which currently must be either a message
  298.     to be sent to the XText object or a sequence of actions.  The args are
  299.     the same as parse_seq.
  300. */
  301.  
  302. XTAction *parse_action(const char **p, NXZone *z,
  303.                        const char *start, ErrorStream *errs)
  304. {
  305.     unsigned char c;
  306.  
  307.     c = skip_whitespace(p);
  308.     if (ALPHA(c))
  309.         return parse_msg(p, z, start, errs);
  310.     if (c == '{')
  311.         return parse_seq(p, z, start, errs);
  312.     report_syntax_error(((c == 0) ? " (unexpected end)" : ""),
  313.                         *p, start, errs);
  314.     return nil;
  315. }
  316.  
  317. /*    parse_keys parses a specification of the keys an action is to be bound
  318.     to.  A specification is a ','-separated sequence, terminated by a '=',
  319.     where each element is zero or more modifiers ('c', 's', 'a', or 'm')
  320.     followed by either a hex key code or ' followed by the character generated
  321.     by the key.  In the latter case there may be several keys that generate
  322.     the character; each is added to the set.  If there are no errors, the
  323.     key codes are stored in keys and true is returned; otherwise false is
  324.     returned.  If there are fewer than MAX_KEYS keys, the first unused entry
  325.     in keys is set to 0 (which happens to be an invalid key code).
  326. */
  327.  
  328. BOOL parse_keys(keySet keys, const char **p,
  329.                 const char *start, ErrorStream *errs)
  330. {
  331.     int num_keys = 0;
  332.     int key = 0;
  333.     unsigned char c; 
  334.     BOOL found_one;
  335.     char *error;
  336.     unsigned char bad_hex;
  337.  
  338.     while (1) {
  339.         c = skip_whitespace(p);
  340.         found_one = NO;
  341.         switch (c) {
  342.         case 'l': key |= 1; break; 
  343.         case 's': key |= 2; break; 
  344.         case 'c': key |= 4; break;
  345.         case 'a': key |= 8; break;
  346.         case 'm': key |= 16; break;
  347.         case 'n': key |= 32; break;
  348.         case 'h': key |= 64; break;
  349.         case '0': case '1': case '2': case '3': case '4': 
  350.         case '5': case '6': case '7': case '8': case '9': 
  351.         case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
  352.             key += HexToDecimal(c, &bad_hex) << (4 + NUM_MASKS);
  353.             c = *++*p;
  354.             key += HexToDecimal(c, &bad_hex) << NUM_MASKS;
  355.             if(bad_hex) {
  356.                 error = " (bad hex code) ";
  357.                 goto syntax_error;
  358.             }
  359.             if (num_keys >= MAX_KEYS) {
  360.                 error = " (too many keys)";
  361.                 goto syntax_error;
  362.             }
  363.             keys[num_keys++] = key;
  364.             found_one = YES;
  365.             break;
  366.         case '\'':
  367.             c = *++*p;
  368.             key += RealChar(c, key) << NUM_MASKS; // deals with cntl chars
  369.             keys[num_keys++] = key;
  370.             found_one = YES;
  371.             break;
  372.         default:
  373.             error = "";
  374.             goto syntax_error;
  375.         }
  376.         ++*p;
  377.         if (found_one) {
  378.             c = skip_whitespace(p);
  379.             ++*p;
  380.             if (c == ',')
  381.                 {}                    // go back for more
  382.             else if (c == '=') {
  383.                 if (num_keys < MAX_KEYS)
  384.                     keys[num_keys] = 0;
  385.                 return YES;
  386.             } else {
  387.                 error = "";
  388.                 goto syntax_error;
  389.             }
  390.         }
  391.     }
  392.     
  393. syntax_error:
  394.     report_syntax_error(error, *p, start, errs);
  395.     return NO;
  396. }                    
  397.  
  398. @implementation XTDispatchAction(parsing)
  399.  
  400. /*    Finally, here's the method we've been preparing to implement.
  401.     Note that any XTActions generated will be allocated in the same
  402.     zone as the dispatch action.
  403. */
  404.  
  405. - addBindings:(const char *)bindings estream:errs
  406. {
  407.     keySet keys;
  408.     char c;
  409.     const char *cp = bindings;
  410.     XTAction *a;
  411.     NXZone *z = [self zone];
  412.     int i;
  413.  
  414.     if (!errs) errs = [ErrorStream default];
  415.     while (1) {
  416.         c = skip_whitespace(&cp);
  417.         if (c == 0)
  418.             return self;
  419.         if (c == ';')
  420.             ++cp;
  421.         else {
  422.             if (!parse_keys(keys, &cp, bindings, errs))
  423.                 return self;
  424.             if (!(a = parse_action(&cp, z, bindings, errs)))
  425.                 return self;
  426.             for (i = 0; i < MAX_KEYS; ++i) {
  427.                 if (keys[i])
  428.                     [self bindKey:keys[i] toAction:a estream:errs];
  429.                 else
  430.                     break;
  431.             }
  432.         }
  433.     }
  434. }
  435.  
  436. @end
  437.